#include <iostream>
#include "IsoBandPolygons.h"
#include "OCMain/Timer.h"

class IsoLine
{
public:
    static IsoLine *createInstance()
    {
        return new IsoLine();
    }

    static void releaseInstance(IsoLine *ptr)
    {
        delete ptr;
    }

    Cell prepareCell(std::vector<double> &data, double threshold,
                     int width, int x, int y, bool linearRing, bool noFrame)
    {
        Cell cell;
        cell.x = x;
        cell.y = y;

        int cval = 0;
        double x0 = data[y * width + x];
        double x1 = data[y * width + x + 1];
        double x2 = data[(y + 1) * width + x + 1];
        double x3 = data[(y + 1) * width + x];

        cval |= x3 >= threshold ? 8 : 0;
        cval |= x2 >= threshold ? 4 : 0;
        cval |= x1 >= threshold ? 2 : 0;
        cval |= x0 >= threshold ? 1 : 0;
        cval = +cval;

        cell.x0 = x0;
        cell.x1 = x1;
        cell.x2 = x2;
        cell.x3 = x3;
        cell.cval = cval;

        if (std::isnan(x0) || std::isnan(x1) || std::isnan(x2) || std::isnan(x3))
        {
            cell.cval = -1;
            return cell;
        }

        double left, right, top, bottom, average;

        switch (cval)
        {
        case 0:
            break;
        case 15:
            /* cell is outside (above) threshold, no polygons */
            break;

        case 14 /* 1110 */:
            left = linear(x0, x3, threshold);
            bottom = linear(x0, x1, threshold);
            cell.edges.data["left"] = {
                {0, left,
                 bottom, 0},
                0,
                -1,
                "top"};
            break;
        case 13 /* 1110 */:
            bottom = linear(x0, x1, threshold);
            right = linear(x1, x2, threshold);
            cell.edges.data["bottom"] = {
                {bottom, 0,
                 1, right},
                1,
                0,
                "left"};
            break;

        case 11 /* 1011 */:
            right = linear(x1, x2, threshold);
            top = linear(x3, x2, threshold);
            cell.edges.data["right"] = {
                {1, right,
                 top, 1},
                0,
                1,
                "bottom"};
            break;

        case 7 /* 0111 */:
            left = linear(x0, x3, threshold);
            top = linear(x3, x2, threshold);
            cell.edges.data["top"] = {
                {
                    top,
                    1,
                    0,
                    left,
                },
                -1,
                0,
                "right"};
            break;

        case 1 /* 0001 */:
            left = linear(x0, x3, threshold);
            bottom = linear(x0, x1, threshold);
            cell.edges.data["bottom"] = {
                {bottom, 0,
                 0, left},
                -1,
                0,
                "right"};
            break;

        case 2 /* 0010 */:
            bottom = linear(x0, x1, threshold);
            right = linear(x1, x2, threshold);

            cell.edges.data["right"] = {
                {1, right,
                 bottom, 0},
                0,
                -1,
                "top"};
            break;

        case 4 /* 0100 */:
            right = linear(x1, x2, threshold);
            top = linear(x3, x2, threshold);

            cell.edges.data["top"] = {
                {top, 1,
                 1, right},
                1,
                0,
                "left"};

            break;

        case 8 /* 1000 */:
            left = linear(x0, x3, threshold);
            top = linear(x3, x2, threshold);

            cell.edges.data["left"] = {
                {0, left,
                 top, 1},
                0,
                1,
                "bottom"};

            break;

        case 12 /* 1100 */:
            left = linear(x0, x3, threshold);
            right = linear(x1, x2, threshold);

            cell.edges.data["left"] = {
                {0, left,
                 1, right},
                1,
                0,
                "left"};

            break;

        case 9 /* 1001 */:
            bottom = linear(x0, x1, threshold);
            top = linear(x3, x2, threshold);
            cell.edges.data["bottom"] = {
                {bottom, 0,
                 top, 1},
                0,
                1,
                "bottom"};
            break;

        case 3 /* 0011 */:
            left = linear(x0, x3, threshold);
            right = linear(x1, x2, threshold);

            cell.edges.data["right"] = {
                {1, right,
                 0, left},
                -1,
                0,
                "right"};

            break;

        case 6 /* 0110 */:
            bottom = linear(x0, x1, threshold);
            top = linear(x3, x2, threshold);
            cell.edges.data["top"] = {
                {top, 1,
                 bottom, 0},
                0,
                -1,
                "top"};
            break;

        case 10 /* 1010 */:
            left = linear(x0, x3, threshold);
            right = linear(x1, x2, threshold);
            bottom = linear(x0, x1, threshold);
            top = linear(x3, x2, threshold);
            average = (x0 + x1 + x2 + x3) / 4;

            if (average < threshold)
            {
                cell.edges.data["left"] = {
                    {0, left,
                     top, 1},
                    0,
                    1,
                    "bottom"};
                cell.edges.data["right"] = {
                    {1, right,
                     bottom, 0},
                    0,
                    -1,
                    "top"};
            }
            else
            {
                cell.edges.data["right"] = {
                    {1, right,
                     top, 1},
                    0,
                    1,
                    "bottom"};
                cell.edges.data["left"] = {
                    {0, left,
                     bottom, 0},

                    0,
                    -1,
                    "top"};
            }

            break;

        case 5 /* 0101 */:
            left = linear(x0, x3, threshold);
            right = linear(x1, x2, threshold);
            bottom = linear(x0, x1, threshold);
            top = linear(x3, x2, threshold);
            average = (x0 + x1 + x2 + x3) / 4;

            if (average < threshold)
            {
                cell.edges.data["bottom"] = {
                    {bottom, 0,
                     0, left},
                    -1,
                    0,
                    "right"};
                cell.edges.data["top"] = {{top, 1, 1, right},
                                          1,
                                          0,
                                          "left"};
            }
            else
            {
                cell.edges.data["top"] = {
                    {top, 1,
                     0, left},
                    -1,
                    0,
                    "right"};
                cell.edges.data["bottom"] = {
                    {bottom, 0,
                     1, right},
                    1,
                    0,
                    "left"};
            }

            break;
        }

        return cell;
    }

    const emscripten::val
    isoLines(const emscripten::val &jsData,
             const int &width, const int &height,
             const double &threshold,
             const emscripten::val &jsGridBox,
             const emscripten::val &options)
    {
        bool flipY = false;
        bool flatten = false;
        bool linearRing = false;
        bool noFrame = true;
        if (!options.isNull())
        {
            if (!options["flipY"].isNull())
            {
                flipY = options["flipY"].as<bool>();
            }
            if (!options["flatten"].isNull())
            {
                flatten = options["flatten"].as<bool>();
            }
            if (!options["linearRing"].isNull())
            {
                linearRing = options["linearRing"].as<bool>();
            }
            if (!options["noFrame"].isNull())
            {
                noFrame = options["noFrame"].as<bool>();
            }
        }

        emscripten::val ret;

        std::vector<IsoBandResult> resultList;

        double west = jsGridBox[0].as<double>();
        double south = jsGridBox[1].as<double>();
        double east = jsGridBox[2].as<double>();
        double north = jsGridBox[3].as<double>();

        std::vector<double> gridData;
        gridData.resize(height * width);

        emscripten::val view(emscripten::typed_memory_view(
            gridData.size(),
            gridData.data()));
        view.call<void>("set", jsData);

        std::vector<std::vector<Cell>>
            cellGrid;
        cellGrid.resize(width - 1);

        for (int i = 0; i < width - 1; ++i)
        {
            cellGrid[i].resize(height - 1);
            for (int j = 0; j < height - 1; ++j)
            {
                cellGrid[i][j] = prepareCell(gridData, threshold, width, i, j, linearRing, noFrame);
            }
        }

        auto linePolygons = traceLinePaths(gridData, width, height, cellGrid, threshold, linearRing, noFrame);
        auto coords = linePolygons[0].coords;

        // for (const auto &f : coords)
        // {
        //     std::cout << f.x << "\t" << f.y << "\n";
        // }
        // std::cout << "333:" << coords.size() << "\n";
        ret = emscripten::val::array();

        double originalWidth = east - west;
        double originalHeigth = north - south;
        double scaleX = originalWidth / (width - 1);
        double scaleY = originalHeigth / (height - 1);

        auto properties = emscripten::val::object();
        properties.set("isoLine", threshold);

        auto polygonCount = 0;
        for (const auto &lineString : linePolygons)
        {
            auto coords = gen_coords(lineString.coords, flipY, west, south, north, scaleX, scaleY);

            auto geometry = emscripten::val::object();
            geometry.set("type", "LineString");

            geometry.set("coordinates", coords);

            auto feature = emscripten::val::object();
            feature.set("type", "Feature");
            feature.set("geometry", geometry);
            feature.set("properties", properties);
            ret.set(polygonCount++, feature);
        }

        return ret;
    }
};
